//
//	MiscMatrix.m -- a class to implement variable-sized matrices
//		Written by Mike Ferris (c) 1994 by Mike Ferris.
//		Modified from original MOKit "MOMatrix" class by Don Yacktman.
//				Version 1.0.  All rights reserved.
//
//		This notice may not be removed from this source code.
//
//	This object is included in the MiscKit by permission from the author
//	and its use is governed by the MiscKit license, found in the file
//	"LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
//	for a list of all applicable permissions and restrictions.
//	

// MiscMatrix is a subclass of Matrix that allows independantly sizable
// rows and columns.  Each row can have a different height and each column
// can have a different width.

#import <misckit/MiscMatrix.h>
#import <objc/objc-runtime.h>

#define CLASS_VERSION 		0
#define CLASS_NAME			"MiscMatrix"

// These are the private methods we use in MiscMatrix
@interface MiscMatrix(private)

- _Misc_copyRowSizes:rs andColSizes:cs zone:(NXZone *)zone;
- _Misc_moveColumnsRightBy:(NXCoord)difference startingAt:(int)col;
- _Misc_moveRowsDownBy:(NXCoord)difference startingAt:(int)row;

@end

@implementation MiscMatrix

+ initialize
// Set the class version
{
	if (self == objc_lookUpClass(CLASS_NAME))  {
		[self setVersion:CLASS_VERSION];
	}
	return self;
}

- setupStorage:(int)rowsHigh :(int)colsWide
// Set up our storage objects
{
	int i;
	MiscColumnSize newCSize;
	MiscRowSize newRSize;
	
	columnSizes = [[Storage allocFromZone:[self zone]] initCount:0 
				elementSize:sizeof(MiscColumnSize)
				description:MISC_COLUMNSIZE_DESC];
	rowSizes = [[Storage allocFromZone:[self zone]] initCount:0 
				elementSize:sizeof(MiscRowSize)
				description:MISC_ROWSIZE_DESC];
	
	newCSize.width = cellSize.width;
	newRSize.height = cellSize.height;
	for (i=0; i<colsWide; i++)  {
		newCSize.x = bounds.origin.x + (i*cellSize.width) + 
					(i*intercell.width);
		[columnSizes addElement:&newCSize];
	}
	for (i=0; i<rowsHigh; i++)  {
		newRSize.y = bounds.origin.y + (i*cellSize.height) + 
					(i*intercell.height);
		[rowSizes addElement:&newRSize];
	}
	
	return self;
}

- initFrame:(const NXRect *)frm mode:(int)aMode prototype:cellId 
			numRows:(int)rowsHigh numCols:(int)colsWide
// Designated initializer override from Matrix.  Sets up our storage stuff.
{
	[super initFrame:frm mode:aMode prototype:cellId numRows:numRows 
				numCols:numCols];
	
	[self setupStorage:rowsHigh :colsWide];
	
	return self;
}

- initFrame:(const NXRect *)frm mode:(int)aMode cellClass:factoryId 
			numRows:(int)rowsHigh numCols:(int)colsWide
// Designated initializer override from Matrix.  Sets up our storage stuff.
{
	[super initFrame:frm mode:aMode cellClass:factoryId numRows:numRows 
				numCols:numCols];
	
	[self setupStorage:rowsHigh :colsWide];
	
	return self;
}

- _Misc_copyRowSizes:rs andColSizes:cs zone:(NXZone *)zone
{
	rowSizes = [rs copyFromZone:zone];
	columnSizes = [cs copyFromZone:zone];
	return self;
}

- copyFromZone:(NXZone *)zone
{
	id obj = [super copyFromZone:zone];
	[obj _Misc_copyRowSizes:rowSizes andColSizes:columnSizes zone:zone];
	return obj;
}

- free
// free the storage
{
	[columnSizes free];
	[rowSizes free];
	return [super free];
}

- _Misc_moveColumnsRightBy:(NXCoord)difference startingAt:(int)col
// A private method used by the methods which cause sizing stuff to change
{
	int i;

	if ((col < 0) || (col >= numCols))  {
		return nil;
	}

	for (i=col; i<numCols; i++)  {
		MiscColumnSize *cSize;
		cSize = (MiscColumnSize *)[columnSizes elementAt:i];
		if (cSize)  cSize->x += difference;
	}
	return self;
}

- _Misc_moveRowsDownBy:(NXCoord)difference startingAt:(int)row
// A private method used by the methods which cause sizing stuff to change
{
	int i;
	
	if ((row < 0) || (row >= numRows))  {
		return nil;
	}

	for (i=row; i<numRows; i++)  {
		MiscRowSize *rSize;
		rSize = (MiscRowSize *)[rowSizes elementAt:i];
		if (rSize)  rSize->y += difference;
	}
	return self;
}

- setWidth:(NXCoord)newWidth ofCol:(int)col
// This method allows the setting of column widths
{
	NXCoord diff;
	MiscColumnSize *cSize;
	
	if ((col < 0) || (col >= numCols))  {
		return nil;
	}

	cSize = (MiscColumnSize *)[columnSizes elementAt:col];
	diff = newWidth - cSize->width;
	cSize->width = newWidth;
	[self _Misc_moveColumnsRightBy:diff startingAt:col+1];
	
	return self;
}

- setHeight:(NXCoord)newHeight ofRow:(int)row
// This method allows the setting of row heights
{
	NXCoord diff;
	MiscRowSize *rSize;
	
	if ((row < 0) || (row >= numRows))  {
		return nil;
	}

	rSize = (MiscRowSize *)[rowSizes elementAt:row];
	diff = newHeight - rSize->height;
	rSize->height = newHeight;
	[self _Misc_moveRowsDownBy:diff startingAt:row+1];
		
	return self;
}

- sizeToCells
// Resize the matrix to the proper size to fit all our cells.
{
	NXRect rect;
	MiscColumnSize *cSize;
	MiscRowSize *rSize;
	
	[self getFrame:&rect];
	
	if (numCols == 0)  {
		rect.size.width = 0.0;
	}  else  {
		cSize = (MiscColumnSize *)[columnSizes lastElement];
		rect.size.width = cSize->x + cSize->width - bounds.origin.x;
	}

	if (numRows == 0)  {
		rect.size.height = 0.0;
	}  else  {
		rSize = (MiscRowSize *)[rowSizes lastElement];
		rect.size.height = rSize->y + rSize->height - bounds.origin.y;
	}
	
	[self setFrame:&rect];

	return self;
}

- renewRows:(int)newRows cols:(int)newCols
// Makes sure to keep our storage objects in synch with everything else.
{
	MiscColumnSize newCSize, *cSize;
	MiscRowSize newRSize, *rSize;
	int i;
	
	// Remove any storage elements past the new number of cols
	for (i=numCols-1; i>=newCols; i--)  {
		[columnSizes removeLastElement];
	}
	// Add any needed new storage elements to get up to the new number of cols
	for (i=numCols; i<newCols; i++)  {
		if (i==0)  {
			newCSize.x = bounds.origin.x;
		}  else  {
			cSize = (MiscColumnSize *)[columnSizes lastElement];
			newCSize.x = cSize->x + cSize->width + intercell.width;
		}
		newCSize.width = cellSize.width;
		[columnSizes addElement:&newCSize];
	}

	// Remove any storage elements past the new number of rows
	for (i=numRows-1; i>=newRows; i++)  {
		[rowSizes removeLastElement];
	}
	// Add any needed new storage elements to get up to the new number of rows
	for (i=numRows; i<newRows; i++)  {
		if (i==0)  {
			newRSize.y = bounds.origin.y;
		}  else  {
			rSize = (MiscRowSize *)[rowSizes lastElement];
			newRSize.y = rSize->y + rSize->height + intercell.height;
		}
		newRSize.height = cellSize.height;
		[rowSizes addElement:&newRSize];
	}
	
	[super renewRows:newRows cols:newCols];
	
	return self;
}


- insertColAt:(int)col
// Keep the storage in synch
{
	MiscColumnSize newCSize, *cSize;
	
	if (col < 0)  {
		return nil;
	}
	
	if (col <= numCols)  {
		[super insertColAt:col];

		if (col > 0)  {
			cSize = (MiscColumnSize *)[columnSizes elementAt:col];
			newCSize.x = cSize->x;
		}  else  {
			newCSize.x = bounds.origin.x;
		}
		newCSize.width = cellSize.width;
		[columnSizes insertElement:&newCSize at:col];
		
		[self _Misc_moveColumnsRightBy:newCSize.width + intercell.width 
					startingAt:col+1];
	}  else  {
		return nil;
	}
	return self;
}

- insertRowAt:(int)row
// Keep the storage in synch
{
    MiscRowSize newRSize, *rSize;
    
    if ((row < 0) || (row > numRows))
	return nil;
    
    if (row == numRows)
    {
	if (numRows == 0)
	    newRSize.y = bounds.origin.y;
	else
	{
	    rSize = (MiscRowSize *)[rowSizes elementAt:row-1];
	    newRSize.y = rSize->y + rSize->height + intercell.height;
	}
    }
    else
    {
	rSize = (MiscRowSize *)[rowSizes elementAt:row];
	newRSize.y = rSize->y;
    }
    newRSize.height = cellSize.height;
    [super insertRowAt:row];
    [rowSizes insertElement:&newRSize at:row];
	    
    [self _Misc_moveRowsDownBy:newRSize.height + intercell.height 
		    startingAt:row+1
    ];
    return self;
}

- removeColAt:(int)col andFree:(BOOL)flag
// Keep the storage in synch
{
	MiscColumnSize *cSize;
	NXCoord diff;
	
	if ((col >= numCols) || (col < 0))  {
		return nil;
	}
	
	[super removeColAt:col andFree:flag];

	cSize = (MiscColumnSize *)[columnSizes elementAt:col];
	diff = cSize->width;
	[columnSizes removeElementAt:col];
	[self _Misc_moveColumnsRightBy:0.0 - diff - intercell.width
			startingAt:col];
	
	return self;
}

- removeRowAt:(int)row andFree:(BOOL)flag
// Keep the storage in synch
{
	MiscRowSize *rSize;
	NXCoord diff;
	
	if ((row >= numRows) || (row < 0))  {
		return nil;
	}

	[super removeRowAt:row andFree:flag];

	rSize = (MiscRowSize *)[rowSizes elementAt:row];
	diff = rSize->height;
	[rowSizes removeElementAt:row];
	[self _Misc_moveRowsDownBy:0.0 - diff - intercell.height startingAt:row];
	
	return self;
}

- drawSelf:(const NXRect *)rects:(int)rectCount
// We do our own drawing because we need to draw our cells in diverse 
// rectangles
{
	int i, j;
	NXRect cFrm;
	
	[window disableFlushWindow];
	
	// the background (if any)
	if (backgroundGray != -1.0)  {
		PSsetgray(backgroundGray);
		if (rectCount==1)  {
			NXRectFill(&(rects[0]));
		}  else  {
			NXRectFill(&(rects[1]));
			NXRectFill(&(rects[2]));
		}
	}
	
	// The cells
	for (i=0; i<numRows; i++)  {
		for (j=0; j<numCols; j++)  {
			[self getCellFrame:&cFrm at:i:j];
			if (rectCount == 1)  {
				if (NXIntersectsRect(&(rects[0]), &cFrm))  {
					[self drawCellAt:i:j];
				}
			}  else  {
				if ((NXIntersectsRect(&(rects[1]), &cFrm)) || 
							(NXIntersectsRect(&(rects[2]), &cFrm)))  {
					[self drawCellAt:i:j];
				}
			}
		}
	}

	[window reenableFlushWindow];
	[window flushWindow];
	
	return self;
}

- getCellFrame:(NXRect *)theRect at:(int)row:(int)col
// Calculate and return the rect used to display the cell at the given
// row and column
{
	MiscColumnSize *cSize;
	MiscRowSize *rSize;
	
	if (col < numCols)  {
		cSize = (MiscColumnSize *)[columnSizes elementAt:col];
		theRect->origin.x = cSize->x;
		theRect->size.width = cSize->width;
	}  else  {
		int num = col - numCols;
		
		cSize = (MiscColumnSize *)[columnSizes lastElement];
		theRect->origin.x = cSize->x +
					(num * (cellSize.width + intercell.width));
		theRect->size.width = cellSize.width;
	}
	
	if (row < numRows)  {
		rSize = (MiscRowSize *)[rowSizes elementAt:row];
		theRect->origin.y = rSize->y;
		theRect->size.height = rSize->height;
	}  else  {
		int num = row - numRows;
		
		rSize = (MiscRowSize *)[rowSizes lastElement];
		theRect->origin.y = rSize->y +
					(num * (cellSize.height + intercell.height));
		theRect->size.height = cellSize.height;
	}
	
	return self;
}

- getRow:(int *)row andCol:(int *)col forPoint:(const NXPoint *)aPoint
// Calculate the row and column of the cell which contains the given point
{
	MiscColumnSize *cSize;
	MiscRowSize *rSize;
	int i;
	
	*row = -1;
	*col = -1;
	if ((aPoint->x < bounds.origin.x) || 
				(aPoint->x > bounds.origin.x + bounds.size.width) || 
				(aPoint->y < bounds.origin.y) || 
				(aPoint->y > bounds.origin.y + bounds.size.height))  {
		return nil;
	}
	for (i=0; i<numCols; i++)  {
		cSize = (MiscColumnSize *)[columnSizes elementAt:i];
		if ((aPoint->x >= cSize->x) && 
					(aPoint->x <= cSize->x + cSize->width))  {
			*col = i;
			break;
		}
	}
	for (i=0; i<numRows; i++)  {
		rSize = (MiscRowSize *)[rowSizes elementAt:i];
		if ((aPoint->y >= rSize->y) && 
					(aPoint->y <= rSize->y + rSize->height))  {
			*row = i;
			break;
		}
	}
	return ((*row == -1) || (*col == -1)) ? nil : self;
}

- setIntercell:(const NXSize *)aSize
// Keep the storage in synch
{
	NXCoord xDiff = aSize->width - intercell.width;
	NXCoord yDiff = aSize->height - intercell.height;
	MiscRowSize *rSize;
	MiscColumnSize *cSize;
	int i;
	
	for (i=1; i<numRows; i++)  {
		rSize = (MiscRowSize *)[rowSizes elementAt:i];
		rSize->y += (yDiff * i);
	}
	for (i=1; i<numCols; i++)  {
		cSize = (MiscColumnSize *)[columnSizes elementAt:i];
		cSize->x += (xDiff * i);
	}
	
	return [super setIntercell:aSize];
}


- write:(NXTypedStream *)typedStream
// Write our ivars
{
	[super write:typedStream];
	NXWriteObject(typedStream, columnSizes);
	NXWriteObject(typedStream, rowSizes);
	return self;
}

- read:(NXTypedStream *)typedStream
// Read our ivars
{
	int classVersion;

	[super read:typedStream];
	
	classVersion = NXTypedStreamClassVersion(typedStream, CLASS_NAME);
	
	switch (classVersion)  {
		case 0:		// First version.
			columnSizes = NXReadObject(typedStream);
			rowSizes = NXReadObject(typedStream);
			break;
		default:
			NXLogError("[%s read:] class version %d cannot read "
						"instances archived with version %d", 
						CLASS_NAME, CLASS_VERSION, classVersion);
			[self setupStorage:numRows :numCols];
			break;
	}
	return self;
}


// ********************Overridden private methods***********************
// *****************that I'm going to hell for using********************

// These methods are used by Matrix's mouseDown:.  Doing the whole 
// mouseDown: method over would have been a royal pain in the butt, 
// so I cheated.


- (BOOL)_mouseHit:(const NXPoint *)forpoint row:(int *)row col:(int *)col
{
	NXPoint	point;
	id		ret;

	point = *forpoint;
	[self convertPoint:&point fromView:nil];
	ret = [self getRow:row andCol:col forPoint:&point];

	if (ret == nil)
		return NO;
	return YES;
}

- (BOOL)_loopHit:(const NXPoint *)forpoint row:(int *)row col:(int *)col
{
	NXPoint	point;
	id		ret;

	point = *forpoint;
	[self convertPoint:&point fromView:nil];
	ret = [self getRow:row andCol:col forPoint:&point];

	if (ret == nil)
		return NO;
	return YES;
}

- (BOOL)_radioHit:(const NXPoint *)forpoint row:(int *)row col:(int *)col
{
	NXPoint	point;
	id		ret;

	point = *forpoint;
	[self convertPoint:&point fromView:nil];
	ret = [self getRow:row andCol:col forPoint:&point];

	if (ret == nil)
		return NO;
	return YES;
}

- sizeRowsToFitCells
{
    NXRect	veryBig;
    NXSize	size;
    NXRect	myFrame;
    NXCoord	maxHeight;
    NXCoord	offset = 0.0;
    MiscRowSize	*rSize;
    int		i,j;
    
    veryBig.size.width = cellSize.width;
    veryBig.size.height = MAXFLOAT;
    
    [self getFrame:&myFrame];
    for (i = 0; i < numRows; i++)
    {
	maxHeight = 0.0;
	rSize = (MiscRowSize *)[rowSizes elementAt:i];
	rSize->y = offset;
	for (j = 0; j < numCols; j++)
	{
	    [[self cellAt:i :j] calcCellSize:&size inRect:&veryBig];
	    if (size.height > maxHeight)
		maxHeight = size.height;
	}
	rSize->height = maxHeight;
	offset += maxHeight + intercell.height;
    }
    
    myFrame.size.height = offset;
    [self setFrame:&myFrame];
    return self;
}
    

- sizeTo:(NXCoord)width :(NXCoord)height
{
    NXRect	oldFrame;
    NXCoord	dx, dy;
    float	sx, sy;
    
    printf ("sizeTo\n");
    [self getFrame:&oldFrame];
    
    [super sizeTo:width :height];

    dx = width - oldFrame.size.width;
    dy = height - oldFrame.size.height;

    if ([self doesAutosizeCells] && (numRows > 0) && (numCols > 0))
    {
	NXCoord		offset;
	int		i;
	MiscRowSize	*rSize;
	MiscColumnSize	*cSize;
	
	sx = 1 + (dx/(oldFrame.size.width - (numRows - 1)*intercell.width));
	sy = 1 + (dy/(oldFrame.size.height - (numCols - 1)*intercell.height));
	
	for (i=0, offset = 0.0; i < numRows; i++)
	{
	    rSize = (MiscRowSize *)[rowSizes elementAt:i];
	    rSize->y = offset;
	    rSize->height *= floor(sy+0.5);
	    offset += rSize->height + intercell.height;
	}
	
	for (i=0, offset = 0.0; i < numCols; i++)
	{
	    cSize = (MiscColumnSize *)[columnSizes elementAt:i];
	    cSize->x = offset;
	    cSize->width *= sx;
	    offset += cSize->width + intercell.width;
	}
    }
    return self;
}


@end

@implementation Storage(MiscLastElementCategory)
	
- (void *)lastElement
// A little shortcut
{
	return [self elementAt:numElements-1];
}
	
@end

